// Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
// This source file is part of the Cangjie project, licensed under Apache-2.0
// with Runtime Library Exception.
//
// See https://cangjie-lang.cn/pages/LICENSE for license information.

#include <fstream>
#include <algorithm>
#include <iostream>

#include "Platform/System.h"

#include "Actions/Action.h"
#include "FileContent.h"

using namespace CangjieMigrator;
using namespace Platform;

bool StringRange::IsValid() const
{
    return end >= start;
}

size_t StringRange::Length() const
{
    return end - start;
}

FileContentLine::FileContentLine(const Path &file)
{
    std::ifstream inStream(file, std::ios::in);
    if (!inStream) {
        throw std::runtime_error("Failed to open file: " + file.string());
    }

    content = std::string((std::istreambuf_iterator<char>(inStream)), std::istreambuf_iterator<char>());
    if (content.empty() || content.back() != '\n') {
        content += System::NewLine;
        needToRemoveEndLine = true;
    } else {
        needToRemoveEndLine = false;
    }
    lineStarts = GenerateLineStarts(content);
}

size_t FileContentLine::GetPositionIndex(const Cangjie::Position &position) const
{
    const auto line = position.line;
    if (line < 1 || line >= static_cast<int>(lineStarts.size())) {
        return 0;
    }
    const auto start = lineStarts[line];
    // lineStarts.back() is not good consider content.size() - 1
    const auto end = (line + 1 < lineStarts.size()) ? lineStarts[line + 1] : lineStarts.back();
    const auto column = position.column - 1;  // Column begin at 1 but there need begin at 0
    if (column >= end - start) {
        return start;
    }
    return start + column;
}

StringRange FileContentLine::GetStringRange(const PositionRange &range) const
{
    const auto start = GetPositionIndex(range.start);
    const auto end = GetPositionIndex(range.end);
    return {start, end};
}

struct Update {
    StringRange range;
    std::string newExpr;

    Update(const StringRange range, std::string newExpr) : range(range), newExpr(std::move(newExpr))
    {}
    [[nodiscard]] size_t BeginIndex() const
    {
        return range.start;
    }

    [[nodiscard]] size_t EndIndex() const
    {
        return range.end;
    }

    [[nodiscard]] size_t Length() const
    {
        return range.Length();
    }

    static bool Compare(const Update &left, const Update &right)
    {
        return left.EndIndex() > right.EndIndex();
    }
};

static bool ActionCompare(const Actions::Action &left, const Actions::Action &right)
{
    const auto leftRange = GetRange(left);
    const auto rightRange = GetRange(right);
    if (leftRange.end.line != rightRange.end.line) {
        return leftRange.end.line > rightRange.end.line;
    }

    bool result = false;
    Dispatch(left,
        [&](const Actions::Replace &) {
            Dispatch(right,
                // Both are Replace: compare column positions
                [&](const Actions::Replace &) { result = leftRange.end.column > rightRange.end.column; },
                // Replace vs Comment: Replace should come first
                [&](const Actions::Comment &) { result = true; });
        },
        [&](const Actions::Comment &) {
            Dispatch(right,
                // Comment vs Replace: Replace should come first
                [&](const Actions::Replace &) { result = false; },
                // Both are Comment: compare column positions
                [&](const Actions::Comment &) { result = leftRange.end.column > rightRange.end.column; });
        });
    return result;
}

void FileContentLine::Apply(std::vector<Actions::Action> actions)
{
    std::sort(actions.begin(), actions.end(), ActionCompare);
    for (const auto &action : actions) {
        Dispatch(action,
            [this](const Actions::Replace &replace) { Apply(replace); },
            [this](const Actions::Comment &comment) { Apply(comment); });
    }
}

void FileContentLine::Apply(const FileImportInfo::Result &importResult)
{
    if (!importResult.IsValid()) {
        std::cerr << "Update import info failed because of invalid range " <<
            importResult.beginLine << " to " << importResult.endLine << std::endl;
        return;
    }
    if (importResult.endLine == importResult.beginLine) {
        content = content.insert(lineStarts[importResult.beginLine], importResult.GenerateString());
        return;
    }

    const auto begin = lineStarts[importResult.beginLine];
    const auto end = lineStarts[importResult.endLine];
    if (importResult.lines.empty()) {
        content.replace(begin, end - begin, "");
    } else {
        content.replace(begin, end - begin, importResult.GenerateString());
    }
}

bool FileContentLine::WriteTo(const Path &file) const
{
    const auto directory = file.parent_path();
    if (!exists(directory)) {
        try {
            create_directories(directory);
        } catch (const FileSystem::filesystem_error &e) {
            std::cerr << "Error creating directory " << directory << " : " << e.what() << std::endl;
            return false;
        }
    }
    if (!exists(directory)) {
        std::cerr << "Error creating directory " << directory << std::endl;
        return false;
    }

    std::ofstream outStream(file, std::ios::out | std::ios::binary);
    if (!outStream) {
        std::cerr << "Open output stream of file " << file << " failed." << std::endl;
        return false;
    }

    outStream.write(content.data(), content.size() - (needToRemoveEndLine ? System::NewLine.size() : 0));
    return true;
}

std::string FileContentLine::SubStr(size_t index, size_t length) const
{
    return content.substr(index, length);
}

std::string FileContentLine::SubStr(StringRange range) const
{
    return SubStr(range.start, range.Length());
}

std::string FileContentLine::operator[](const PositionRange &range) const
{
    return SubStr(GetStringRange(range));
}

std::vector<size_t> FileContentLine::GenerateLineStarts(const std::string &content)
{
    auto result = std::vector<size_t>();
    result.emplace_back(0);
    const auto length = content.size();
    if (length == 0) {
        result.emplace_back(0);
        return result;
    }
    for (auto i = 0, newLineStartPos = 0; i < length;) {
        while (i < length && !(content[i] == '\r' && i + 1 < length && content[i + 1] == '\n') &&
            content[i] != '\n') {
            i++;
        }
        if (i < length) {
            if (content[i] == '\r' && i + 1 < length && content[i + 1] == '\n') {
                i += 2; // Jump 2 char for '\r\n'
            } else {
                i++;
            }
        }
        result.emplace_back(newLineStartPos);
        newLineStartPos = i;
    }
    if (content[length - 1] == '\n') {
        result.emplace_back(length);
    }
    return result;
}

void FileContentLine::Apply(const Actions::Replace &replace)
{
    const auto range = GetStringRange(replace.range);
    if (!range.IsValid()) {
        return;
    }
    content.replace(range.start, range.Length(), replace.update);
}

void FileContentLine::Apply(const Actions::Comment &comment)
{
    const auto line = comment.range.start.line;
    if (line <= 0 || line >= lineStarts.size()) {
        std::cerr << "Failed to add comment \"" << comment.Generate() << "\" at " << comment.range << "." << std::endl;
        return;
    }
    const auto index = lineStarts[line];

    // Count leading spaces in content from the index
    size_t leadingSpaces = 0;
    while (index + leadingSpaces < content.size() && std::isspace(content[index + leadingSpaces])) {
        ++leadingSpaces;
    }

    // Add the same number of leading spaces to the comment
    const auto indentedComment = std::string(leadingSpaces, ' ') + comment.Generate() + Platform::System::NewLine;
    content.replace(index, 0, std::move(indentedComment));
}
